package com.ezandroid.alphago.engine;

import android.util.SparseArray;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;

/**
 * Feature48
 *
 * @author like
 * @date 2017-07-23
 */
public class Feature48 {

    public static byte BLACK = 1;
    public static byte WHITE = -1;
    public static byte EMPTY = 0;

    /********************Feature48 Start*******************************/
    private static final byte BOARD = 0;
    private static final byte ONES = 1;
    private static final byte TURNS_SINCE = 2;
    private static final byte LIBERTIES = 3;
    private static final byte CAPTURE_SIZE = 4;
    private static final byte SELF_ATARI_SIZE = 5;
    private static final byte LIBERTIES_AFTER = 6;
    private static final byte LADDER_CAPTURE = 7;
    private static final byte LADDER_ESCAPE = 8;
    private static final byte SENSIBLENESS = 9;
    private static final byte ZEROS = 10;

    private static final byte[] FEATURE_COUNT = {3, 1, 8, 8, 8, 8, 8, 1, 1, 1, 1};
    public static final byte[] FEATURE_START = {0, 3, 4, 12, 20, 28, 36, 44, 45, 46, 47};

    private final byte[] mBoardFeature;// 0:3
    // 1:1
    private final byte[] mHistoryFeature; // 2:8
    private final byte[] mLibertyFeature; // 3:8
    private final byte[] mCaptureSizeFeature; // 4:8
    private final byte[] mSelfAtariSizeFeature; // 5:8
    private final byte[] mLibertyAfterFeature; // 6:8
    private final byte[] mLadderCaptureFeature; // 7:1
    private final byte[] mLadderEscapeFeature; // 8:1
    // 9:1
    // 10:1
    /********************Feature48 End*******************************/

    private byte mActivePlayer;

    private final int mBoardSize;

    private final SparseArray<List<Integer>> NEIGHBOR_CACHE;

    private final SparseArray<List<Integer>> DIAGONAL_CACHE;

    private int mKOPos;

    private MoveManager mMoveManager;

    public Feature48() {
        mBoardSize = 361;
        mBoardFeature = new byte[361];
        mHistoryFeature = new byte[361];
        mLibertyFeature = new byte[361];
        mCaptureSizeFeature = new byte[361];
        mSelfAtariSizeFeature = new byte[361];
        mLibertyAfterFeature = new byte[361];
        mLadderCaptureFeature = new byte[361];
        mLadderEscapeFeature = new byte[361];
        NEIGHBOR_CACHE = new SparseArray<>();
        DIAGONAL_CACHE = new SparseArray<>();
        mMoveManager = new MoveManager(19);

        for (int i = 0; i < 361; i++) {
            mSelfAtariSizeFeature[i] = -1;
            mHistoryFeature[i] = -1;
            if (i == 0 || i == 18 || i == 342 || i == 360) {
                mLibertyAfterFeature[i] = 2;
            } else if (i / 19 == 0 || i / 19 == 18 || i % 19 == 0 || i % 19 == 18) {
                mLibertyAfterFeature[i] = 3;
            } else {
                mLibertyAfterFeature[i] = 4;
            }

            // 初始化邻接棋子缓存
            List<Integer> neighbors = new ArrayList<>();
            if (isOnBoard(top(i))) {
                neighbors.add(top(i));
            }
            if (isOnBoard(bottom(i))) {
                neighbors.add(bottom(i));
            }
            if (isOnBoard(left(i)) && (i % 19 != 0)) {
                neighbors.add(left(i));
            }
            if (isOnBoard(right(i)) && (i % 19 != 18)) {
                neighbors.add(right(i));
            }
            NEIGHBOR_CACHE.put(i, neighbors);

            // 初始化斜接棋子缓存
            List<Integer> diagonals = new ArrayList<>();
            if (isOnBoard(topLeft(i))) {
                diagonals.add(topLeft(i));
            }
            if (isOnBoard(bottomLeft(i))) {
                diagonals.add(bottomLeft(i));
            }
            if (isOnBoard(topRight(i)) && (i % 19 != 0)) {
                diagonals.add(topRight(i));
            }
            if (isOnBoard(bottomRight(i)) && (i % 19 != 18)) {
                diagonals.add(bottomRight(i));
            }
            DIAGONAL_CACHE.put(i, diagonals);
        }
    }

    private boolean isSensibleness(int pos) {
        return isLegal(pos, mActivePlayer) && !isEye(pos, mActivePlayer, new Stack<Integer>());
    }

    private byte getNextPlayer(byte player) {
        return player == BLACK ? WHITE : BLACK;
    }

    private StoneColor getStoneColor(byte player) {
        return player == BLACK ? StoneColor.BLACK : StoneColor.WHITE;
    }

    private byte getPlayer(StoneColor color) {
        return color == StoneColor.BLACK ? BLACK : WHITE;
    }

    private void updateFeature() {
        updateHistoryFeature();
        for (Chain chain : mMoveManager.getChains()) {
            Debug.printChain(chain);
        }
        for (int i = 0; i < 361; i++) {
            if (mBoardFeature[i] == EMPTY) {
                if (isLegal(i, mActivePlayer)) {
                    MoveManager moveManager = new MoveManager(19, mMoveManager.getChains());
                    Set<Stone> deadStones = new HashSet<>();
                    Stone stone = new Stone();
                    stone.color = getStoneColor(mActivePlayer);
                    stone.intersection = new Intersection(i % 19, i / 19);
                    moveManager.addStoneWithHistory(stone, deadStones, false);

                    Chain chain = moveManager.getChain(new Intersection(i % 19, i /
                            19));
                    mLibertyAfterFeature[i] = (byte) chain.getLiberties().size();
                    if (!deadStones.isEmpty()) {
                        mCaptureSizeFeature[i] = (byte) deadStones.size();
                    } else {
                        mCaptureSizeFeature[i] = 0;
                    }
                    if (chain.getLiberties().size() == 1) {
                        mSelfAtariSizeFeature[i] = (byte) chain.getStones().size();
                    } else {
                        mSelfAtariSizeFeature[i] = -1;
                    }
                }
            } else {
                Chain chain = mMoveManager.getChain(new Intersection(i % 19, i /
                        19));
                mLibertyFeature[i] = (byte) chain.getLiberties().size();
            }
        }
    }

    public Move undo() {
        Move move = mMoveManager.undo();
        if (move != null) {
            Stone stone = move.getStone();
            int pos = stone.intersection.x + stone.intersection.y * 19;

            Intersection ko = move.getKO();
            if (ko != null) {
                mKOPos = ko.x + ko.y * 19;
            } else {
                mKOPos = -1;
            }

            mBoardFeature[pos] = EMPTY;
            mLibertyFeature[pos] = 0;

            Set<Stone> captured = move.getCaptured();
            for (Stone capture : captured) {
                int p = capture.intersection.x + capture.intersection.y * 19;
                mBoardFeature[p] = getPlayer(capture.color);
                mLibertyAfterFeature[p] = 0;
                mCaptureSizeFeature[p] = 0;
                mSelfAtariSizeFeature[p] = -1;
            }

            updateFeature();

            Debug.printBoard(mBoardFeature);
        }
        return move;
    }

    public Move redo() {
        Move move = mMoveManager.redo();
        if (move != null) {
            Stone stone = move.getStone();
            int pos = stone.intersection.x + stone.intersection.y * 19;

            Set<Stone> captured = move.getCaptured();
            if (captured.size() == 1) {
                Iterator<Stone> it = captured.iterator();
                if (it.hasNext()) {
                    Stone str = it.next();
                    mKOPos = str.intersection.x + str.intersection.y * 19;
                }
            }

            for (Stone capture : captured) {
                int p = capture.intersection.x + capture.intersection.y * 19;
                mBoardFeature[p] = EMPTY;
                mLibertyFeature[p] = 0;
            }

            mBoardFeature[pos] = getPlayer(stone.color);
            mLibertyAfterFeature[pos] = 0;
            mCaptureSizeFeature[pos] = 0;
            mSelfAtariSizeFeature[pos] = -1;

            updateFeature();

            Debug.printBoard(mBoardFeature);
        }
        return move;
    }

    public MoveManager getMoveManager() {
        return mMoveManager;
    }

    public boolean playMove(int x, int y, byte player) {
        return playMove(x + y * 19, player);
    }

    public boolean playMove(int pos, byte player) {
        if (!isLegal(pos, player)) {
            return false;
        }

        mActivePlayer = getNextPlayer(player);

        Set<Stone> deadStones = new HashSet<>();
        Stone stone = new Stone();
        stone.color = getStoneColor(player);
        stone.intersection = new Intersection(pos % 19, pos / 19);
        mMoveManager.addStone(stone, deadStones);
        stone.number = mMoveManager.getHistory().getHead();

        if (mKOPos != -1) {
            mMoveManager.getHistory().readLatest().setKO(new Intersection(mKOPos % 19,
                    mKOPos / 19)); // 记录上一手的劫位置
        }
        // 劫位置更新
        mKOPos = -1;
        if (deadStones.size() == 1) {
            Iterator<Stone> it = deadStones.iterator();
            if (it.hasNext()) {
                Stone sto = it.next();
                mKOPos = sto.intersection.x + sto.intersection.y * 19;
            }
        }

        for (Stone capture : deadStones) {
            mBoardFeature[capture.intersection.x + capture.intersection.y * 19] = EMPTY;
            mLibertyFeature[capture.intersection.x + capture.intersection.y * 19] = 0;
        }

        mBoardFeature[pos] = player;
        mLibertyAfterFeature[pos] = 0;
        mCaptureSizeFeature[pos] = 0;
        mSelfAtariSizeFeature[pos] = -1;

        updateFeature();

        Debug.printBoard(mBoardFeature);
        return true;
    }

    private void updateHistoryFeature() {
        for (int i = 0; i < 361; i++) {
            mHistoryFeature[i] = -1;
        }
        for (Chain chain : mMoveManager.getChains()) {
            for (Stone stone : chain.getStones()) {
                int pos = stone.intersection.x + stone.intersection.y * 19;
                int turnSince = mMoveManager.getHistory().getHead() - stone.number;
                mHistoryFeature[pos] = turnSince > Byte.MAX_VALUE ?
                        Byte.MAX_VALUE : (byte) turnSince;
            }
        }
    }

    public boolean isLegal(int pos, byte player) {
        if (!isOnBoard(pos)) {
            return false;
        }
        if (mBoardFeature[pos] != EMPTY) {
            return false;
        }
        if (mKOPos == pos) {
            return false;
        }
        if (isSuicide(pos, player)) {
            return false;
        }
        return true;
    }

    private boolean isSuicide(int pos, byte player) {
        Stone stone2 = new Stone();
        stone2.color = getStoneColor(player);
        stone2.intersection = new Intersection(pos % 19, pos / 19);
        return mMoveManager.isSuicide(stone2);
    }

    private boolean isLikeEye(int pos, int owner) {
        if (mBoardFeature[pos] != EMPTY) {
            return false;
        }
        for (int n : NEIGHBOR_CACHE.get(pos)) {
            if (mBoardFeature[n] != owner) {
                return false;
            }
        }
        return true;
    }

    private boolean isEye(int pos, int owner, Stack<Integer> stack) {
        if (!isLikeEye(pos, owner)) {
            return false;
        }
        int numBadDiagonal = 0;
        int allowableBadDiagonal = NEIGHBOR_CACHE.get(pos).size() == 4 ? 1 : 0;
        for (int d : DIAGONAL_CACHE.get(pos)) {
            if (mBoardFeature[d] == -owner) {
                numBadDiagonal += 1;
            } else if (mBoardFeature[d] == EMPTY && !stack.contains(d)) {
                stack.push(pos);
                if (!isEye(d, owner, stack)) {
                    numBadDiagonal += 1;
                }
                stack.pop();
            }
            if (numBadDiagonal > allowableBadDiagonal) {
                return false;
            }
        }
        return true;
    }

    private boolean isOnBoard(int pos) {
        return pos >= 0 && pos <= 360;
    }

    private int topLeft(int pos) {
        return pos - 19 - 1;
    }

    private int topRight(int pos) {
        return pos - 19 + 1;
    }

    private int bottomLeft(int pos) {
        return pos + 19 - 1;
    }

    private int bottomRight(int pos) {
        return pos + 19 + 1;
    }

    private int top(int pos) {
        return pos - 19;
    }

    private int bottom(int pos) {
        return pos + 19;
    }

    private int left(int pos) {
        return pos - 1;
    }

    private int right(int pos) {
        return pos + 1;
    }

    private void setOneHot(byte features[][], int pos, int i, int j) {
        features[pos][FEATURE_START[i] + Math.min(j, FEATURE_COUNT[i] - 1)] = 1;
    }

    public byte[] getBoard() {
        return mBoardFeature;
    }

    public byte[][] generateFeatures() {
        byte features[][] = (byte[][]) Array.newInstance(Byte.TYPE, mBoardSize, 48);
//        Debug.printLibertiesAfter(mLibertyAfterFeature);
//        Debug.printLiberties(mLibertyFeature);
//        Debug.printTurnSince(mHistoryFeature);
//        Debug.printCaptureSize(mCaptureSizeFeature);
//        Debug.printSelfAtariSize(mSelfAtariSizeFeature);
        int pos = 0;
        while (pos < mBoardFeature.length) {
            if (mBoardFeature[pos] == 0) {
                features[pos][FEATURE_START[BOARD] + 2] = 1; // 空001
                if (isLegal(pos, mActivePlayer)) {
                    setOneHot(features, pos, CAPTURE_SIZE, mCaptureSizeFeature[pos]);
                    if (mSelfAtariSizeFeature[pos] > 0) {
                        setOneHot(features, pos, SELF_ATARI_SIZE, mSelfAtariSizeFeature[pos] - 1);
                    }
                    setOneHot(features, pos, LIBERTIES_AFTER, mLibertyAfterFeature[pos] - 1);
                    features[pos][FEATURE_START[LADDER_CAPTURE]] = mLadderCaptureFeature[pos];
                    features[pos][FEATURE_START[LADDER_ESCAPE]] = mLadderEscapeFeature[pos];
                    if (isSensibleness(pos)) {
                        features[pos][FEATURE_START[SENSIBLENESS]] = 1;
                    }
                }
            } else {
                if (mBoardFeature[pos] == mActivePlayer) {
                    features[pos][FEATURE_START[BOARD]] = 1; // 己方棋子100
                } else {
                    features[pos][FEATURE_START[BOARD] + 1] = 1; // 敌方棋子010
                }
                setOneHot(features, pos, TURNS_SINCE, mHistoryFeature[pos]);
                setOneHot(features, pos, LIBERTIES, mLibertyFeature[pos] - 1);
            }
            features[pos][FEATURE_START[ONES]] = 1;
            pos++;
        }
        return features;
    }
}
